﻿using System;
using System.Activities;
using System.Activities.Presentation.PropertyEditing;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
using WFExtended.Activities.Designers;
using WFExtended.Framework.Constraints;
using WFExtended.Framework.Presentation;
using WFExtended.Framework.ServiceModel;

namespace WFExtended.Activities
{
    /// <summary>
    /// Base class for a Wcf Operation Invoker Activity
    /// </summary>
    public abstract class AWcfOperationInvoker : NativeActivity
    {
        /// <summary>
        /// Get or Set the WCF Proxy
        /// </summary>
        public abstract InArgument<AWcfProxy> Proxy { get; set; }
    }

    /// <summary>
    /// Invoke a Operation Service Method
    /// <code>
    /// DelegateInArgument<WcfProxy<IMockService>> proxy = new DelegateInArgument<WcfProxy<IMockService>>(); <br />
    /// Activity workflow = new WcfConfigurationProxy<IMockService>() <br />
    /// &nbsp; { <br />
    /// &nbsp; &nbsp; Body = new System.Activities.ActivityAction<WcfProxy<IMockService>>() <br />
    /// &nbsp; &nbsp;  { <br />
    /// &nbsp; &nbsp; &nbsp; Argument = proxy, <br />
    /// &nbsp; &nbsp; &nbsp; Handler = new WcfOperationInvoker() <br />
    /// &nbsp; &nbsp; &nbsp; {<br /> 
    /// &nbsp; &nbsp; &nbsp; &nbsp; OperationName = "Ping",<br />
    /// &nbsp; &nbsp; &nbsp; &nbsp; Proxy = proxy <br />
    /// &nbsp; &nbsp; &nbsp; } <br />
    /// &nbsp; &nbsp; } <br /> 
    /// &nbsp; }; <br />
    /// </code>
    /// </summary>
    [ToolboxBitmapAttribute(typeof(EntryPoint), "Resources.WcfOperationInvokerToolbox.bmp")]
    [Designer(typeof(WcfOperationInvokerDesigner))]
    [Description("Invoke a Operation Service Method")]
    public class WcfOperationInvoker : WcfOperationInvoker<object>
    {
    }

    /// <summary>
    /// Invoke a Operation Service Method with a result
    /// <code>
    /// DelegateInArgument<WcfProxy<IMockService>> proxy = new DelegateInArgument<WcfProxy<IMockService>>(); <br />
    /// Variable<bool> result = new Variable<bool>(); <br />
    /// Activity workflow = new WcfConfigurationProxy<IMockService>() <br />
    /// &nbsp; { <br />
    /// &nbsp; &nbsp; Body = new System.Activities.ActivityAction<WcfProxy<IMockService>>() <br />
    /// &nbsp; &nbsp;  { <br />
    /// &nbsp; &nbsp; &nbsp; Argument = proxy, <br />
    /// &nbsp; &nbsp; &nbsp; Handler = new WcfOperationInvoker<bool>() <br />
    /// &nbsp; &nbsp; &nbsp; {<br /> 
    /// &nbsp; &nbsp; &nbsp; &nbsp; OperationName = "CheckData",<br />
    /// &nbsp; &nbsp; &nbsp; &nbsp; Proxy = proxy <br />
    /// &nbsp; &nbsp; &nbsp; &nbsp; Result = result <br />
    /// &nbsp; &nbsp; &nbsp; } <br />
    /// &nbsp; &nbsp; } <br /> 
    /// &nbsp; }; <br />
    /// </code>
    /// </summary>
    /// <typeparam name="TResult">Type of the method result</typeparam>
    [ToolboxBitmapAttribute(typeof(EntryPoint), "Resources.WcfOperationInvokerToolbox.bmp")]
    [Designer(typeof(WcfOperationInvokerDesigner))]
    [Description("Invoke a Operation Service Method with a result of type TResult")]
    public class WcfOperationInvoker<TResult> : AWcfOperationInvoker
    {  

        /// <summary>
        /// Constructor
        /// </summary>
        public WcfOperationInvoker()
        {
            BeInsideConstraint<WcfOperationInvoker<TResult>> parentConstraint =
                new BeInsideConstraint<WcfOperationInvoker<TResult>>(false, "Proxy", typeof(IWcfProxyActivity));
            Parameters = new Dictionary<string, InArgument>();
            this.Constraints.Add(parentConstraint.GetConstraint());

        }
        
        /// <summary>
        /// Get or Set the WCF Proxy
        /// <remarks>
        /// Required Argument
        /// </remarks>
        /// </summary>
        [Browsable(true)]
        [RequiredArgument]
        [Category("Configuration")]
        [Description("Get or Set the WCF Proxy")]
        public override InArgument<AWcfProxy> Proxy { get; set; }

        /// <summary>
        /// Get or Set the Operation Name
        /// <remarks>
        /// Required Argument
        /// </remarks>
        /// </summary>
        [Browsable(true)]
        [RequiredArgument]
        [Category("Operation")]
        [Description("Get or Set the Operation Name")]
        public string OperationName { get; set; }

        /// <summary>
        /// Get or Set the Parameter of the operation Method
        /// </summary>
        [Browsable(true)]
        [Category("Operation")]
        [Editor(typeof(DictionaryEditor), typeof(PropertyValueEditor))]
        [Description("Get or Set the Parameter of the Operation Method")]
        public IDictionary<string, InArgument> Parameters { get; set; }

        /// <summary>
        /// Get or the the result of the operation Method
        /// </summary>
        [Browsable(true)]
        [Category("Operation")]
        [Description("Get or the the result of the operation Method")]
        public OutArgument<TResult> Result { get; set; }

        /// <summary>
        /// Creates and validates a description of the activity’s arguments, variables, child activities, and activity delegates.
        /// </summary>
        /// <param name="metadata">The activity’s metadata that encapsulates the activity’s arguments, variables, child activities, and activity delegates.</param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            RuntimeArgument valuesArgument = new RuntimeArgument("Proxy", typeof(AWcfProxy), ArgumentDirection.In, true);
            metadata.Bind(Proxy, valuesArgument);
            metadata.AddArgument(valuesArgument);

            WcfOperationInvokerConstraint<WcfOperationInvoker<TResult>> operationConstraint =
                 new WcfOperationInvokerConstraint<WcfOperationInvoker<TResult>>(false, "Operation", OperationName,
                        Parameters.ToDictionary((KV) => KV.Key, (KV) => KV.Value.ArgumentType));
            this.Constraints.Add(operationConstraint.GetConstraint());

            base.CacheMetadata(metadata);
        }

        /// <summary>
        /// Runs the activity’s execution logic.
        /// </summary>
        /// <param name="context">The execution context in which the activity executes</param>
        protected override void Execute(NativeActivityContext context)
        {
            AWcfProxy proxy = Proxy.Get(context);

            MethodInfo method = ServiceModelHelper.GetOperation(proxy.ProxyType, OperationName).Item1;
            Result.Set(context, 
                method.Invoke(
                proxy.GetType().GetProperty("Client").GetValue(proxy), 
                Parameters.Select((KV) => KV.Value.Get(context)).ToArray()));
        }

    }
}
